package com.xiao.train.business.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.xiao.train.business.domain.SkToken;
import com.xiao.train.business.domain.SkTokenExample;
import com.xiao.train.business.domain.TrainSeatExample;
import com.xiao.train.business.domain.TrainStationExample;
import com.xiao.train.business.enums.RedisKeyPreEnum;
import com.xiao.train.business.mapper.SkTokenMapper;
import com.xiao.train.business.mapper.TrainSeatMapper;
import com.xiao.train.business.mapper.TrainStationMapper;
import com.xiao.train.business.mapper.cust.SkTokenMapperCust;
import com.xiao.train.business.req.SkTokenQueryReq;
import com.xiao.train.business.req.SkTokenSaveReq;
import com.xiao.train.business.resp.SkTokenQueryResp;
import com.xiao.train.common.resp.PageResp;
import com.xiao.train.common.util.SnowUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SkTokenService {

    @Resource
    SkTokenMapper skTokenMapper;

    @Resource
    SkTokenMapperCust skTokenMapperCust;

    @Resource
    TrainStationMapper trainStationMapper;

    @Resource
    TrainSeatMapper trainSeatMapper;

    @Value("${spring.profiles.active}")
    private String env;

    @Resource
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void save(SkTokenSaveReq req) {
        DateTime now = DateTime.now();
        SkToken skToken = BeanUtil.copyProperties(req, SkToken.class);
        if (ObjectUtil.isNull(skToken.getId())) {
            skToken.setId(SnowUtil.getId());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        } else {
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }
    public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req) {
        SkTokenExample skTokenExample = new SkTokenExample();
        SkTokenExample.Criteria criteria = skTokenExample.createCriteria();
        skTokenExample.setOrderByClause("id desc");
        log.info("查询页码: {}", req.getPage());
        log.info("每页条数: {}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<SkToken> skTokens = skTokenMapper.selectByExample(skTokenExample);

        PageInfo<SkToken> pageInfo = new PageInfo<>(skTokens);
        log.info("总行数: {}", pageInfo.getTotal());
        log.info("总页数: {}", pageInfo.getPages());

        PageResp<SkTokenQueryResp> pageResp = new PageResp<>();
        List<SkTokenQueryResp> list = BeanUtil.copyToList(skTokens, SkTokenQueryResp.class);
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    public void delete(Long id) {
        skTokenMapper.deleteByPrimaryKey(id);
    }
    public void genDaily(Date date, String trainCode) {
        log.info("删除 日期{} 车次{} 的令牌信息", date, trainCode);
        SkTokenExample example = new SkTokenExample();
        example.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
        skTokenMapper.deleteByExample(example);
        SkToken skToken = new SkToken();
        Date now = new Date();
        skToken.setCreateTime(now);
        skToken.setUpdateTime(now);
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);
        TrainStationExample trainStationExample = new TrainStationExample();
        trainStationExample.createCriteria().andTrainCodeEqualTo(trainCode);
        long stationCount = trainStationMapper.countByExample(trainStationExample);
        TrainSeatExample trainSeatExample = new TrainSeatExample();
        trainSeatExample.createCriteria().andTrainCodeEqualTo(trainCode);
        long seatCount = trainSeatMapper.countByExample(trainSeatExample);
        int count = (int) (stationCount * seatCount);
        skToken.setCount(count);
        skToken.setId(SnowUtil.getId());
        log.info("插入 日期{} 车次{} 的令牌信息, 初始令牌数:{}", date, trainCode, count);
        skTokenMapper.insert(skToken);
    }

    /**
     * 校验令牌
     * @param date
     * @param trainCode
     * @param memberId
     * @return
     */
    public boolean validSkToken(Date date, String trainCode, Long memberId) {
        log.info("开始校验日期{} 车次{} 会员Id{}的令牌", date, trainCode, memberId);
        if (!env.equals("dev")) {
            String lockKey = RedisKeyPreEnum.SK_TOKEN + "-" + memberId;
            RLock lock = redissonClient.getLock(lockKey);
            try {
                // todo:单个会员id设置5秒超时时间,防止机器人刷票
                if (lock.tryLock(0, 5, TimeUnit.SECONDS)) { //不带看门狗
                    log.info("恭喜,抢到令牌锁了");
                } else {
                    log.info("很遗憾,没抢到令牌锁");
                    return false;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        String countKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + date + "-" + trainCode;
        String count = redisTemplate.opsForValue().get(countKey);
        if (count != null) {
            log.info("有缓存 countKey: {}", countKey);
            Long remain = redisTemplate.opsForValue().decrement(countKey, 1);
            if (remain <= 0) {
                log.info("令牌库存不足");
                return false;
            }
            if (remain % 5 == 0) //todo 细节1:延迟更新,减轻数据库压力
                skTokenMapperCust.decreaseNex(5, date, trainCode);
            return true;
        } else {
            log.info("无缓存 countKey: {}", countKey);
            SkTokenExample example = new SkTokenExample();
            example.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
            List<SkToken> skTokens = skTokenMapper.selectByExample(example);
            if (CollUtil.isEmpty(skTokens)) {
                log.info("数据库不存在 日期{} 车次{} 的令牌", date, trainCode);
                return false;
            }
            SkToken skToken = skTokens.get(0);
            if (skToken.getCount() <= 0) {
                log.info("令牌库存不足");
                return false;
            }
            skToken.setCount(skToken.getCount() - 1);
            Integer cnt = skToken.getCount();
            redisTemplate.opsForValue().set(countKey, cnt.toString(), 60, TimeUnit.SECONDS);
            if (cnt % 5 == 0)
                skTokenMapperCust.decreaseNex(5, date, trainCode);
            return true;
        }
    }
}
